#include "SokobanState.h"
#include <OgreTextureUnitState.h>

//#include "Character.h"
//#include "Inventory.h"

using namespace Ogre;

SokobanState* SokobanState::mSokobanState;

void SokobanState::enter( void ) {
    mRoot             = Root::getSingletonPtr();
    mOverlayMgr       = OverlayManager::getSingletonPtr();
    mInputDevice      = InputManager::getSingletonPtr()->getKeyboard();
	mMouseOverlay     = mOverlayMgr->getByName( "Overlay/MousePointer" );
	mMousePointer     = mOverlayMgr->getOverlayElement( "MousePointer/Pointer" );
	mSceneMgr         = mRoot->getSceneManager( "ST_GENERIC" );
	mSoundEngine	  = irrklang::createIrrKlangDevice();
	//mAudioMgr = new AudioManager(mse);

	//Ogre::Overlay *inventory_bar = mOverlayMgr->getByName( "Allstates/Inventory" );
	//Ogre::Overlay *dialog = mOverlayMgr->getByName( "Allstates/Dialog" );
	//dialog->show();
    //mMouseOverlay->show();

	// Create RaySceneQuery
	mRaySceneQuery = mSceneMgr->createRayQuery(Ray());

	// Default Values
	keyIsDown = solved = false;
	camTrans = Vector3::ZERO;
	playerTrans = Vector3::ZERO;
	yourBox = oppBox = "";
	fireBoxPlaced = waterBoxPlaced = fireSpotTotal = waterSpotTotal = totalStep = 0;
	level_rows = 11;
	level_cols = 11;
	maxH = 40;
	moveSpeed = 40;
	camSpeed = 0.2;
	SpotList = FireColList = WaterColList = WallColList = "begin";

	this->createCamera();
	this->createScene();

	LogManager::getSingleton().logMessage("PlayerFire initial position: ("+Ogre::StringConverter::toString(curPlayer->getPosition())+")");
	LogManager::getSingleton().logMessage("PlayerFire substr(6): "+ curPlayer->getName().substr(6));

	mMouseOverlay->show();

	// all ready? music pls
	this->playMusic( "sounds/bgm_01.mp3", true );

}
void SokobanState::createCamera(void){
    // create camera, but leave at default position
    mCamera = mSceneMgr->createCamera("PlayerCam"); 
	mViewport = mRoot->getAutoCreatedWindow()->addViewport( mCamera );
}
void SokobanState::createScene(void){

	mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));

	// Let there be Lights!
	Light *light = mSceneMgr->createLight("LightDirect");
	light->setType(Light::LT_DIRECTIONAL);
	light->setDirection(Vector3(0, 0, 0));//light->setDirection(1,-1,1);
	light->setDiffuseColour(ColourValue::White); // ColourValue::White or 1,1,1
	light->setSpecularColour(ColourValue::White);

	// Omni light @ East and West
	light = mSceneMgr->createLight("LightPoint1");
	light->setType(Light::LT_POINT);
	light->setPosition(Vector3(-100, 500, 0));
	light->setDiffuseColour(ColourValue::White);
	light->setSpecularColour(ColourValue::White);
	light->setPowerScale(0.5);
	light = mSceneMgr->createLight("LightPoint2");
	light->setType(Light::LT_POINT);
	light->setPosition(Vector3(500, 500, 0));
	light->setDiffuseColour(ColourValue::White);
	light->setSpecularColour(ColourValue::White);
	light->setPowerScale(0.5);

	//SkyBox
	mSceneMgr->setSkyBox(true, "HillSkyBox", 5000, false);

	Entity *ent = mSceneMgr->createEntity("Floor", "FloorMesh.mesh");
	SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("FloorNode");
	node->setPosition(Vector3(200, 0, 200));
	node->attachObject(ent);

	//create a Plane entity on the fly (to be used for the HotSpots)
	MeshManager::getSingleton().createPlane("PlaneSpot",ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Plane(Vector3::UNIT_Y, 5), 37, 37, 1, 1, true, 1, 1, 1, Vector3::UNIT_Z);

	mCamera->setPosition(215, 400, 450);
	mCamera->lookAt(215,0,215);
	//mCamera->pitch(Degree(-90)); //pitch = X axis, Yaw = Z axis, Roll = Y axis

	//arrange a Sokoban level map from an Array
	char level_array[11][11] = { //temp solution to put values instead of a vars; cpp strictly wants the value of level_rows =/
		{'#','#','#','#','#','#','#','#','#','#','#'},
		{'#','#','#','#','#','#','-','1','-','-','#'},
		{'#','#','#','#','#','#','-','#','#','-','#'},
		{'#','#','#','-','f','#','F','#','#','-','#'},
		{'#','#','#','-','-','-','-','-','-','F','#'},
		{'#','-','-','-','-','#','-','-','-','-','#'},
		{'#','W','-','-','-','-','-','-','#','#','#'},
		{'#','-','#','#','W','#','w','-','#','#','#'},
		{'#','-','#','#','-','#','#','#','#','#','#'},
		{'#','-','-','2','-','#','#','#','#','#','#'},
		{'#','#','#','#','#','#','#','#','#','#','#'},
	};

	LogManager::getSingleton().logMessage("******   Sokoban Map parser  ******");
	int xPos = 0;
	int zPos = 0;
	int count = 0;
	for(int i = 0; i<level_rows; i++){
		//LogManager::getSingleton().logMessage("****** Row "+Ogre::StringConverter::toString(i));
		for(int j = 0; j<level_cols; j++){
			//LogManager::getSingleton().logMessage("****** Column "+Ogre::StringConverter::toString(j));
			// block types
			switch(level_array[i][j]){
				case '#':	ent = mSceneMgr->createEntity("Wall_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "ConcBox.mesh");
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("WallNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("Wall_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							WallColList +=" ";
							WallColList += "WallNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							break;	
				case 'F':	ent = mSceneMgr->createEntity("FireBox_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "FireBox.mesh");
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("FireBoxNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("FireBox_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							FireColList +=" ";
							FireColList += "FireBoxNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							break;	
				case 'f':	ent = mSceneMgr->createEntity("FireNodeSpot_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "PlaneSpot");
							ent->setMaterialName("Spot_Fire");
							ent->setCastShadows(false);		
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("FireNodeSpotNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							ent = mSceneMgr->createEntity("FireNodePilar_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "FireBox.mesh");
							ent->setMaterialName("Yellow_trans");
							ent->setCastShadows(false);			
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("FireNodePilarNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							ent->setVisible(false);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("FireNodeSpot_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							//add to SpotList
							//SpotList.push_back("SpotFireNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							//SpotList[count] = "SpotFireNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							//SpotList[count] = node;
							SpotList +=" ";
							SpotList += "FireNodeSpotNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							fireSpotTotal+=1;
							break;
				case '1':	ent = mSceneMgr->createEntity("PlayerFire", "PlayerFire.mesh");
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("PlayerFireNode");
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							//node->showBoundingBox(true);
							pfQuat = node->getOrientation();
							LogManager::getSingleton().logMessage("PlayerFire");
							break;	
				case 'W':	ent = mSceneMgr->createEntity("WaterBox_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "WaterBox.mesh");
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("WaterBoxNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("WaterBox_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							WaterColList +=" ";
							WaterColList += "WaterBoxNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							break;	
				case 'w':	ent = mSceneMgr->createEntity("WaterNodeSpot_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "PlaneSpot");
							ent->setMaterialName("Spot_Water");
							ent->setCastShadows(false);		
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("WaterNodeSpotNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							ent = mSceneMgr->createEntity("WaterNodePilar_"+Ogre::StringConverter::toString(i)+Ogre::StringConverter::toString(j), "FireBox.mesh");
							ent->setMaterialName("Blue_trans");
							ent->setCastShadows(false);			
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("WaterNodePilarNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							ent->setVisible(false);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("WaterNodeSpot_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							//add to SpotList
							//SpotList.push_back("SpotWaterNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							//SpotList[count] = "SpotWaterNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							//SpotList[count] = node;
							SpotList +=" ";
							SpotList += "WaterNodeSpotNode_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j);
							waterSpotTotal+=1;
							break;
				case '2':	ent = mSceneMgr->createEntity("PlayerWater", "PlayerWater.mesh");
							node = mSceneMgr->getRootSceneNode()->createChildSceneNode("PlayerWaterNode");
							node->setPosition(Vector3(xPos,0,zPos));
							node->attachObject(ent);
							//node->showBoundingBox(true);
							LogManager::getSingleton().logMessage("PlayerWater");
							break;
				default:	//LogManager::getSingleton().logMessage("Wall_"+Ogre::StringConverter::toString(i)+"_"+Ogre::StringConverter::toString(j));
							break;
			}
			if(j==(level_cols-1)){ xPos = 0; }else{ xPos += 40;}
		}
		zPos +=40; //precise size = 39.37
		count++;
	}
	curPlayer = mSceneMgr->getSceneNode("PlayerFireNode"); //default Player
	yourBox = "FireNode"; oppBox = "WaterNode";
	// Splitting the lists
	SpotListSplit = Ogre::StringUtil::split( SpotList, " ", 0 ); 
	FireColListSplit = Ogre::StringUtil::split( FireColList, " ", 0 ); 
	WaterColListSplit = Ogre::StringUtil::split( WaterColList, " ", 0 ); 
	WallColListSplit = Ogre::StringUtil::split( WallColList, " ", 0 ); 

	//LogManager::getSingleton().logMessage("SpotList size = "+Ogre::StringConverter::toString( SpotListSplit.size() ));
}

void SokobanState::exit( void ) {
	mMouseOverlay->hide();
    mSceneMgr->clearScene();
    mSceneMgr->destroyAllCameras();
    mRoot->getAutoCreatedWindow()->removeAllViewports();
}

void SokobanState::pause( void ) {
	mMouseOverlay->hide();
}

void SokobanState::resume( void ) {
	mMouseOverlay->show();
	this->playMusic( "sounds/bgm_01.mp3", true );
}

void SokobanState::update( unsigned long lTimeElapsed ) {	

	if(keyIsDown){
		mCamera->setPosition(mCamera->getPosition()+camTrans * lTimeElapsed);
		//curPlayer->translate(curPlayer->getOrientation() * playerTrans * lTimeElapsed);
	}
	
	//animate spot nodes
	for(int i = 0; i<SpotListSplit.size(); i++){
		if( i!= 0 && SpotListSplit[i] != "" && !SpotListSplit[i].empty() ){
			curNode = mSceneMgr->getSceneNode(SpotListSplit[i]);
			curNode->yaw(Degree(2));
			if(curNode->getPosition().y < maxH ){
				curNode->setPosition(curNode->getPosition()+(Vector3(0,1,0)));
				maxH = 40;
			}else{
				curNode->setPosition(curNode->getPosition()+(Vector3(0,-1,0)));
				maxH = 1;
			}
			//LogManager::getSingleton().logMessage(SpotListSplit[i]);
			//LogManager::getSingleton().logMessage("Spot index: "+Ogre::StringConverter::toString(i));
		}
	}

	if(!solved){
		if(fireBoxPlaced==fireSpotTotal && waterBoxPlaced==waterSpotTotal){
			fireSpotTotal=0;waterSpotTotal=0;
			// You have solved the puzzle! ... somehow
			this->stopMusic();
			this->playSFX("sounds/tadaa.mp3");
			// display "puzzle solved" overlay and show level menu
			// 
		}
	}	

}

void SokobanState::keyPressed( const OIS::KeyEvent &e ) {

	if( e.key == OIS::KC_ESCAPE ) {	this->requestShutdown(); }
	if( e.key == OIS::KC_SPACE ){ 
		//this->stopMusic();
	}
	if( e.key == OIS::KC_1 ) {
		static_cast<Entity*>(curPlayer->getAttachedObject(0))->setCastShadows(true);
		curPlayer = mSceneMgr->getSceneNode("PlayerFireNode");
		static_cast<Entity*>(curPlayer->getAttachedObject(0))->setCastShadows(false);
	}
	if( e.key == OIS::KC_2 ) {	
		static_cast<Entity*>(curPlayer->getAttachedObject(0))->setCastShadows(true);
		curPlayer = mSceneMgr->getSceneNode("PlayerWaterNode");
		static_cast<Entity*>(curPlayer->getAttachedObject(0))->setCastShadows(false);
	}
	if(!solved){
		if(e.key == OIS::KC_W){
			playerTrans.z -= moveSpeed;
			keyIsDown = true;
			//pDir = 'u';
		}else
		if(e.key == OIS::KC_S){
			playerTrans.z += moveSpeed;
			keyIsDown = true;
			//pDir = 'd';
		}else
		if(e.key == OIS::KC_D){
			playerTrans.x += moveSpeed;
			keyIsDown = true;
			//pDir = 'r';
		}else
		if(e.key == OIS::KC_A){
			playerTrans.x -= moveSpeed;
			keyIsDown = true;
			//pDir = 'l';
		}
	}

	if(e.key == OIS::KC_UP){
		camTrans.z -= camSpeed;
		keyIsDown = true;
	}else
	if(e.key == OIS::KC_DOWN){
		camTrans.z += camSpeed;
		keyIsDown = true;
	}else
	if(e.key == OIS::KC_RIGHT){
		camTrans.x += camSpeed;
		keyIsDown = true;
	}else
	if(e.key == OIS::KC_LEFT){
		camTrans.x -= camSpeed;
		keyIsDown = true;
	}
	if(e.key == OIS::KC_HOME){
		camTrans.y += camSpeed;
		keyIsDown = true;
	}else
	if(e.key == OIS::KC_END){
		camTrans.y -= camSpeed;
		keyIsDown = true;
	}
	if(e.key == OIS::KC_PGUP){
		mCamera->pitch(Degree(5));
		keyIsDown = true;
	}else
	if(e.key == OIS::KC_PGDOWN){
		mCamera->pitch(Degree(-5));
		keyIsDown = true;
	}
	if(e.key == OIS::KC_ADD){
		//this->increaseMusicVolume(1.0);
		this->setAudioVolume(this->getAudioVolume()+0.1);
	}
	if(e.key == OIS::KC_SUBTRACT){
		this->setAudioVolume(this->getAudioVolume()-0.1);
	}

	if(keyIsDown){
		//debug
		LogManager::getSingleton().logMessage("Attempt to move: ("+Ogre::StringConverter::toString(curPlayer->getPosition())+") -> ("+Ogre::StringConverter::toString(curPlayer->getPosition()+playerTrans)+")");
		//Check Collisions
		if(Ogre::StringUtil::match(curPlayer->getName(), "PlayerFire*", true)){
			yourBox = "FireBox"; oppBox = "WaterBox";
		}else{
			yourBox = "WaterBox"; oppBox = "FireBox";
		}
		if( checkRayHitWithNode(curPlayer->getPosition()+playerTrans, "WallNode") 
			|| checkRayHitWithNode(curPlayer->getPosition()+playerTrans, oppBox)
			|| checkRayHitWithNode(curPlayer->getPosition()+playerTrans, "Player") ){
			//LogManager::getSingleton().logMessage("Hit a Wall!");
		}else{
			if( checkRayHitWithNode(curPlayer->getPosition()+playerTrans, yourBox) ){
				curBox = lastHitNode;
				//tempTrans = playerTrans*2;
				//check if the box is touching anything else (wall, boxes or other player)
				if(!checkRayHitWithNode(curBox->getPosition()+playerTrans, "WallNode") 
					&& !checkRayHitWithNode(curBox->getPosition()+playerTrans, "Box") 
					&& !checkRayHitWithNode(curBox->getPosition()+playerTrans, "Player") )
				{
					// check for a box on the right spot, if it does, add score
					// TODO: dont forget to substract the score when a box WAS in the right spot and going OUT of the spot
					if(checkRayHitWithNode(curBox->getPosition()+playerTrans, curPlayer->getName().substr(6)+"Spot")){
						if(Ogre::StringUtil::match(curPlayer->getName(), "*Fire*", true)){
							fireBoxPlaced +=1 ;
							if(checkRayHitWithNode(curBox->getPosition()+playerTrans, curPlayer->getName().substr(6)+"Pilar")){
								lastHitNode->getAttachedObject(0)->setVisible(true);
							}
							this->playSFX( "sounds/ding_liftup.mp3"); 
						}else if(Ogre::StringUtil::match(curPlayer->getName(), "*Water*", true)){
							waterBoxPlaced +=1 ;
							if(checkRayHitWithNode(curBox->getPosition()+playerTrans, curPlayer->getName().substr(6)+"Pilar")){
								lastHitNode->getAttachedObject(0)->setVisible(true);
							}
							this->playSFX( "sounds/ding_liftup.mp3" );
						}
					}else if(checkRayHitWithNode(curBox->getPosition()+playerTrans, "Spot")){ //wrong spot, buddy.. xD
							if(checkRayHitWithNode(curBox->getPosition()+playerTrans, "Pilar")){
								Entity *ent = static_cast<Entity*>(lastHitNode->getAttachedObject(0));
								ent->setMaterialName("Red_trans");
								ent->setVisible(true);
							}
						this->playSFX( "sounds/buzz_smooth.mp3");
					}
					//update positions
					curBox->translate(playerTrans);
					curPlayer->translate(playerTrans);
					
				}
			}else{
				curPlayer->translate(playerTrans);
			}

			// update score
			totalStep += 1;
		}
	}
	
}

void SokobanState::keyReleased( const OIS::KeyEvent &e ) {
	camTrans = playerTrans = Vector3::ZERO;
	
	if(	e.key == OIS::KC_W	|| 
		e.key == OIS::KC_S	|| 
		e.key == OIS::KC_D	|| 
		e.key == OIS::KC_A	||
		e.key == OIS::KC_UP ||
		e.key == OIS::KC_DOWN||
		e.key == OIS::KC_LEFT||
		e.key == OIS::KC_RIGHT
		){
			keyIsDown = false;
			//pDir = '';
	}

	if( e.key == OIS::KC_SPACE ) {
        this->pushState( PauseState::getSingletonPtr() );
    }

}

void SokobanState::mouseMoved( const OIS::MouseEvent &e ) {
    const OIS::MouseState &mouseState = e.state;
	mMousePointer->setTop( mouseState.Y.abs );
	mMousePointer->setLeft( mouseState.X.abs );
}

//void frameStarted( const Ogre::FrameEvent &evt ){	//mInputDevice->capture();}
//void frameStarted( const Ogre::FrameEvent &evt ){}

void SokobanState::mousePressed( const OIS::MouseEvent &e, OIS::MouseButtonID id ) {

}
void SokobanState::mouseReleased( const OIS::MouseEvent &e, OIS::MouseButtonID id ) {

}
bool SokobanState::checkRayHitWithNode( Ogre::Vector3 vect, Ogre::String str ){
  
	Vector3 vStart = Ogre::Vector3(vect.x, 300, vect.z);
	Vector3 vEnd = Vector3::NEGATIVE_UNIT_Y; //Ogre::Vector3(vect.x, -100, vect.z);
	Ogre::Ray ray( vStart, Vector3::NEGATIVE_UNIT_Y);
	
	// Query for this ray
	mRaySceneQuery->setRay( ray );
	mRaySceneQuery->setSortByDistance( true );
	mRaySceneQuery->setQueryTypeMask(mSceneMgr->ENTITY_TYPE_MASK);

	// Execute query
	Ogre::RaySceneQueryResult result = mRaySceneQuery->execute();
	//Ogre::RaySceneQueryResult::iterator itr;
	//LogManager::getSingleton().logMessage("Ray: ("+Ogre::StringConverter::toString(vStart)+") -> ("+Ogre::StringConverter::toString(vEnd)+")");

	// Get results
	bool found = false;

	for (size_t ri = 0; ri < result.size(); ri++){
		//LogManager::getSingleton().logMessage("Hit");
		if ( result[ri].movable ) {
			// get the entity to check
			//Ogre::Entity *pentity = static_cast<Ogre::Entity*>(itr->movable);
			Ogre::SceneNode *pNode = result[ri].movable->getParentSceneNode();
			//LogManager::getSingleton().logMessage("Hit: "+pNode->getName()); //hit list, lol >:D
			if(Ogre::StringUtil::match(pNode->getName(), "*"+str+"*", true)){
				//LogManager::getSingleton().logMessage("Hit: "+pNode->getName());
				lastHitNode = pNode;
				found = true;
				break;
			}
		}
	}

	return found;
}

SokobanState* SokobanState::getSingletonPtr( void ) {
    if( !mSokobanState ) {
        mSokobanState = new SokobanState();
    }

    return mSokobanState;
}